/*
 * Copyright (c) 2011 Jean-Marc Perronne
 * Ensisa, 12 rue des Freres Lumieres, F-68093 Mulhouse cedex
 * All rights reserved.
 *
 * The copyright of this software is owned by Werner Randelshofer. 
 * You may not use, copy or modify this software, except in  
 * accordance with the license agreement you entered into with  
 * Jean-Marc Perronne. For details see accompanying license terms. 
 */

package fr.uha.ensisa.jmp.drone.ui.cockpit;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JSeparator;
import javax.swing.JSlider;

import ch.randelshofer.media.avi.AVIOutputStream;
import ch.randelshofer.media.quicktime.QuickTimeOutputStream;
import fr.uha.ensisa.jmp.drone.app.DroneConstants;
import fr.uha.ensisa.jmp.drone.at_commands.AtCommands;
import fr.uha.ensisa.jmp.drone.at_commands.AtConfigCommand;
import fr.uha.ensisa.jmp.drone.handler.DroneHandler;
import fr.uha.ensisa.jmp.drone.navdata.options.NavdataDemo;
import fr.uha.ensisa.jmp.drone.navdata.options.OptionsVistor;
import fr.uha.ensisa.jmp.drone.navdata.options.VisionDetect;
import fr.uha.ensisa.jmp.drone.recorder.configuration.ConfigFileRecorder;
import fr.uha.ensisa.jmp.drone.recorder.configuration.ConfigFileWriter;
import fr.uha.ensisa.jmp.drone.recorder.configuration.DroneConfigRecorder;
import fr.uha.ensisa.jmp.drone.recorder.navdata.NavdataDemoRecorder;
import fr.uha.ensisa.jmp.drone.recorder.picture.PictureRecorder;
import fr.uha.ensisa.jmp.drone.recorder.video.AviWriter;
import fr.uha.ensisa.jmp.drone.recorder.video.MovWriter;
import fr.uha.ensisa.jmp.drone.recorder.video.VideoRecorder;
import fr.uha.ensisa.jmp.drone.ui.instrument.Compass;
import fr.uha.ensisa.jmp.drone.ui.instrument.Horizon;
import fr.uha.ensisa.jmp.drone.ui.instrument.linear.LinearIndicator;
import fr.uha.ensisa.jmp.drone.video.ImageConsumer;
import fr.uha.ensisa.jmp.drone.video.VideoChannel;
import fr.uha.ensisa.jmp.drone.vision.TagsDraft;
import fr.uha.ensisa.jmp.ui.utils.ColoredRange;
import fr.uha.ensisa.jmp.ui.utils.ColoredRanges;
import fr.uha.ensisa.jmp.ui.utils.JMSwingUtilities;

public class Cockpit extends JFrame
{
	private final static int TO_DEG = 1000;
	private final static int TO_M = 100;

	private final static int V_MAX = 3000;
	private final static int V_MIN = -3000;

	private final static Color DEFAULT_BACKGROUND_COLOR = Color.DARK_GRAY;
	private final static Color SKY_COLOR = new Color(0, 204, 255);
	private final static Color GROUND_COLOR = new Color(153, 102, 51);
	private final static Dimension VIDEO_SIZE = new Dimension(320, 240);

	private final static MovWriter MOV_WRITER = new MovWriter(QuickTimeOutputStream.VideoFormat.JPG);
	private final static AviWriter AVI_WRITER = new AviWriter(AVIOutputStream.VideoFormat.JPG);
	private final static ConfigFileRecorder CONFIG_FILE_RECORDER = new ConfigFileRecorder(new ConfigFileWriter(),DroneConstants.CONFIGS_RECORDS_DIR, DroneConstants.CONFIGS_EXT);

	private DroneHandler drone;

	private JMenuBar menuBar;
	
	private JMenu configMenu;
	private JMenuItem getConfigItem;
	private JMenuItem saveConfigItem;
	private JMenuItem sendIndoorConfigItem;
	private JMenuItem sendOutdoorConfigItem;

	private JMenu videoFormatMenu;
	private JRadioButtonMenuItem aviItem;
	private JRadioButtonMenuItem movItem;

	private JPanel actionsPane;
	private JButton trimButton;
	
	private JPanel velocitiesPane;
	private LinearIndicator vxIndicator;
	private LinearIndicator vyIndicator;
	private LinearIndicator vzIndicator;

	private JPanel batteryPane;
	private LinearIndicator batteryIndicator;
	private LinearIndicator altitudeIndicator;

	private JPanel flightInstrumentsPane;
	private Compass compass;
	private Horizon horizon;

	private JPanel videoSelectionPane;
	private JButton nextVideoButton;
	
	private JPanel videoPane;
	private VideoPanel video;

	private JPanel recordVideoPanel;
	private JCheckBox recordVideoDemoCheckBox;
	private JButton takePictureButton;
	private JCheckBox detectTargetCheckBox;

	
	private JPanel continuousActionsPane;
	private JCheckBox recordNavdataDemoCheckBox;

	private JPanel pageEndPane;
	private JPanel pageCenterPane;
	private JPanel pageStartPane;

	private CockpitUpdater cockpitUpdater;

	public Cockpit(DroneHandler drone)
	{
		this.drone = drone;
		this.cockpitUpdater = new CockpitUpdater();
		this.setupActionsPane();
	}

	public void setup()
	{
		this.setupMenuBar();
		this.setupPageStartPane();
		this.setupVelocitiesPane();
		this.setupBatteryPane();
		this.setupPageCenterPane();
		this.setupPageEndPane();

		this.setBackground(DEFAULT_BACKGROUND_COLOR);
		this.addWindowListener(new java.awt.event.WindowAdapter()
		{
			public void windowClosing(WindowEvent winEvt)
			{
				closeCockpit();
			}
		});

		this.pack();
		this.setVisible(true);
	}

	private void setupMenuBar()
	{
		this.menuBar = new JMenuBar();
		
		this.menuBar.add(this.setupConfigMenu());
		this.menuBar.add(this.setupVideoFormatMenu());
	
		this.setJMenuBar(this.menuBar);
	}
	
	private JMenu setupConfigMenu()
	{
		this.configMenu = new JMenu("Configurations");
		
		this.getConfigItem = new JMenuItem("Get configurations");
		this.getConfigItem.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent arg0)
			{
				if (drone != null)
				{
					drone.sendScheduled(AtCommands.GET_CONFIGURATION);
				}
			}
		});
		
		this.saveConfigItem = new JMenuItem("Save Configurations");
		this.saveConfigItem.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent arg0)
			{
				if (drone != null)
				{
					DroneConfigRecorder configRecorder = (DroneConfigRecorder) drone.getConfigConsumer(DroneConstants.DRONE_CONFIG_RECORDER_KEY);
					if (configRecorder == null) return;
					try
					{
						CONFIG_FILE_RECORDER.start();
						CONFIG_FILE_RECORDER.record(configRecorder.getConfig());
						CONFIG_FILE_RECORDER.stop();
					}
					catch (IOException e)
					{
						e.printStackTrace();
					}
				}
			}
		});
		
		this.sendIndoorConfigItem = new JMenuItem("Send indoor configuration");
		this.sendIndoorConfigItem.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent arg0)
			{
				if (drone != null)
				{
					AtConfigCommand c = new AtConfigCommand();
					c.param("control:outdoor", "FALSE");
					drone.sendScheduled(c);
					c.param("control:flight_without_shell", "FALSE");
					drone.sendScheduled(c);
					c.param("control:altitude_max", "2000");
					drone.sendScheduled(c);
					c.param("control:euler_angle_max", ".25");
					drone.sendScheduled(c);

				}
			}
		});
		
		this.sendOutdoorConfigItem = new JMenuItem("Send outdoor configuration");
		this.sendOutdoorConfigItem.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent arg0)
			{
				if (drone != null)
				{
					AtConfigCommand c = new AtConfigCommand();
					c.param("control:outdoor", "TRUE");
					drone.sendScheduled(c);
					c.param("control:flight_without_shell", "TRUE");
					drone.sendScheduled(c);
					c.param("control:altitude_max", "3000");
					drone.sendScheduled(c);
					c.param("control:euler_angle_max", "3.8424200e-01");
					drone.sendScheduled(c);
				}
			}
		});
		
		this.configMenu.add(this.getConfigItem);
		this.configMenu.add(this.saveConfigItem);
		this.configMenu.add(new JSeparator());
		this.configMenu.add(this.sendIndoorConfigItem);
		this.configMenu.add(this.sendOutdoorConfigItem);
		
		return this.configMenu;
	}
	
	private JMenu setupVideoFormatMenu()
	{
		this.videoFormatMenu = new JMenu("Video Format");
		this.aviItem = new JRadioButtonMenuItem("AVI");
		this.aviItem.setSelected(true);
		
		this.movItem = new JRadioButtonMenuItem("MOV");
		this.movItem.setSelected(false);
			
		ButtonGroup group = new ButtonGroup();
        group.add(this.aviItem);
        group.add(this.movItem);
       
        ActionListener videoFormatActionListener = new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent e)
			{
				if (drone == null) return;
				VideoRecorder videoRecorder = (VideoRecorder) drone.getImageConsumer(DroneConstants.VIDEO_RECORDER_KEY);
				if (videoRecorder == null) return;
				
				if (e.getSource() == aviItem) videoRecorder.setWriter(AVI_WRITER);
				if (e.getSource() == movItem) videoRecorder.setWriter(MOV_WRITER);
			}
		};
         
		this.aviItem.addActionListener(videoFormatActionListener);
		this.movItem.addActionListener(videoFormatActionListener);

		this.videoFormatMenu.add(this.aviItem);
		this.videoFormatMenu.add(this.movItem);
		return this.videoFormatMenu;
	}
	
	private JPanel setupActionsPane()
	{
		this.actionsPane = new JPanel();
		this.actionsPane.setLayout(new FlowLayout());
		//this.actionsPane.setLayout(new BoxLayout(this.actionsPane, BoxLayout.X_AXIS));

		this.trimButton = new JButton("flat trim");

		this.trimButton.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent arg0)
			{
				if (drone != null)
				{
					drone.sendScheduled(AtCommands.F_TRIM);
				}
			}
		});

		this.actionsPane.add(this.trimButton);
		return this.actionsPane;
	}

	private void setupPageStartPane()
	{
		this.pageStartPane = new JPanel();
		this.pageStartPane.setLayout(new BoxLayout(this.pageStartPane, BoxLayout.Y_AXIS));

		this.pageStartPane.add(this.setupActionsPane());
		this.pageStartPane.add(this.setupContinuousActionsPane());
		
		this.actionsPane.setAlignmentX(LEFT_ALIGNMENT);
		this.continuousActionsPane.setAlignmentX(LEFT_ALIGNMENT);

		this.getContentPane().add(this.pageStartPane, BorderLayout.PAGE_START);
	}
	
	private void setupVelocitiesPane()
	{
		this.velocitiesPane = new JPanel();
		this.velocitiesPane.setLayout(new BoxLayout(this.velocitiesPane, BoxLayout.X_AXIS));

		Color c = JMSwingUtilities.invertColor(DEFAULT_BACKGROUND_COLOR);
		ColoredRanges ranges = new ColoredRanges();
		ranges.addRange(new ColoredRange(-5000, 5000, c));

		this.vxIndicator = new LinearIndicator("vx", JSlider.VERTICAL, V_MIN, V_MAX);
		this.vxIndicator.setMinorTickSpacing(500);
		this.vxIndicator.setColorProperties(ranges);
		this.vxIndicator.labelColor(c);

		this.vyIndicator = new LinearIndicator("vy", JSlider.VERTICAL, V_MIN, V_MAX);
		this.vxIndicator.setMinorTickSpacing(500);
		this.vyIndicator.setColorProperties(ranges);
		this.vyIndicator.labelColor(c);

		ColoredRanges vzranges = new ColoredRanges();
		vzranges.addRange(new ColoredRange(0, 49, GROUND_COLOR));
		vzranges.addRange(new ColoredRange(50, 100, SKY_COLOR));

		this.vzIndicator = new LinearIndicator("vz", JSlider.VERTICAL);
		this.vzIndicator.setColorProperties(vzranges);
		this.vzIndicator.labelColor(c);

		this.velocitiesPane.add(this.vxIndicator);
		this.velocitiesPane.add(this.vyIndicator);
		this.velocitiesPane.add(this.vzIndicator);
		this.getContentPane().add(this.velocitiesPane, BorderLayout.WEST);
	}

	private void setupBatteryPane()
	{
		this.batteryPane = new JPanel();
		this.batteryPane.setLayout(new BoxLayout(this.batteryPane, BoxLayout.Y_AXIS));

		Color c = JMSwingUtilities.invertColor(DEFAULT_BACKGROUND_COLOR);

		this.batteryIndicator = new LinearIndicator("battery", JSlider.VERTICAL);
		this.batteryIndicator.setMinorTickSpacing(5);
		this.batteryIndicator.labelColor(c);

		ColoredRanges ranges = new ColoredRanges();
		ranges.addRange(new ColoredRange(0, 15, c));

		this.altitudeIndicator = new LinearIndicator("altitude", JSlider.VERTICAL, 0, 15);
		this.altitudeIndicator.setMajorTickSpacing(2);
		this.altitudeIndicator.setMinorTickSpacing(1);
		this.altitudeIndicator.setColorProperties(ranges);
		this.altitudeIndicator.labelColor(c);

		this.batteryPane.add(this.batteryIndicator);
		this.batteryPane.add(this.altitudeIndicator);
		this.getContentPane().add(this.batteryPane, BorderLayout.EAST);
	}

	private JPanel setupVideoSelectionPane()
	{
		this.videoSelectionPane = new JPanel();
		this.videoSelectionPane.setLayout(new FlowLayout());

		this.nextVideoButton = new JButton(">>");
		this.nextVideoButton.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent arg0)
			{
				if (drone != null)
				{
					AtConfigCommand c = new AtConfigCommand();
					c.param("video:video_channel", String.valueOf(VideoChannel.ZAP_CHANNEL_NEXT.value()));
					drone.sendScheduled(c);
				}
			}
		});

   		this.videoSelectionPane.add(this.nextVideoButton);
		
		return videoSelectionPane;
	}

		
	private JPanel setupFlightInstrumentPane()
	{
		this.flightInstrumentsPane = new JPanel();
		this.flightInstrumentsPane.setLayout(new FlowLayout());

		this.compass = new Compass("pictures/compass_background.png", "pictures/compass_needle.png");
		this.compass.getModel().setMinimum(-180);
		this.compass.getModel().setMaximum(180);

		this.horizon = new Horizon("pictures/horizon_plane.png");
		this.horizon.getRenderer().setSize(new Dimension(200, 200));

		this.flightInstrumentsPane.add(this.compass);
		this.flightInstrumentsPane.add(this.horizon);
		
		return this.flightInstrumentsPane;
	}

	private JPanel setupContinuousActionsPane()
	{
		this.continuousActionsPane = new JPanel();
		this.continuousActionsPane.setLayout(new BoxLayout(this.continuousActionsPane, BoxLayout.Y_AXIS));

		
		this.recordNavdataDemoCheckBox = new JCheckBox("recording navdatademo data");
		this.recordNavdataDemoCheckBox.setForeground(JMSwingUtilities.invertColor(DEFAULT_BACKGROUND_COLOR));
		this.recordNavdataDemoCheckBox.addItemListener(new ItemListener()
		{
			@Override
			public void itemStateChanged(ItemEvent event)
			{
				if (drone == null) return;
				NavdataDemoRecorder recorder = (NavdataDemoRecorder) drone.getOptionVisitor(DroneConstants.NAVDATA_DEMO_RECORDER_KEY);
				if(recorder == null) return;
				try
				{
					if (event.getStateChange() == ItemEvent.SELECTED)
					{
						recorder.start();
						recordNavdataDemoCheckBox.setForeground(Color.RED);
						recordNavdataDemoCheckBox.setText("recording navdatademo data in "+ recorder.getCurrentFileName());
					}
					else
					{
						recorder.stop();
						recordNavdataDemoCheckBox.setForeground(JMSwingUtilities.invertColor(DEFAULT_BACKGROUND_COLOR));
						recordNavdataDemoCheckBox.setText("recording navdatademo data");
					}
				}
				catch(IOException ex)
				{
					ex.printStackTrace();
				}
			}
			
		});
		
		
		this.continuousActionsPane.add(this.recordNavdataDemoCheckBox);
		
		return this.continuousActionsPane;
	}
	
	private JPanel setupVideoPane()
	{
		this.videoPane = new JPanel();
		this.videoPane.setLayout(new BoxLayout(this.videoPane, BoxLayout.Y_AXIS));

		this.video = new VideoPanel(VIDEO_SIZE);

		this.videoPane.add(this.video);

		return this.videoPane;
	}

	private JPanel setupRecordVideoPanel()
	{
		this.recordVideoPanel = new JPanel();
		this.recordVideoPanel.setLayout(new FlowLayout());


		ImageIcon leftButtonIcon = new ImageIcon("pictures/red_square.gif");
		this.takePictureButton = new JButton("picture", leftButtonIcon);
		this.takePictureButton.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent arg0)
			{
				if (drone != null)
				{
					PictureRecorder pictureRecorder = (PictureRecorder) drone.getImageConsumer(DroneConstants.PICTURE_RECORDER_KEY);
					if (pictureRecorder != null)
					{
						try
						{
							pictureRecorder.start();
						}
						catch (IOException e)
						{
							e.printStackTrace();
						}
					}
				}
			}
		});
		
		
		this.recordVideoDemoCheckBox = new JCheckBox("recording video");
		this.recordVideoDemoCheckBox.setForeground(JMSwingUtilities.invertColor(DEFAULT_BACKGROUND_COLOR));
		
		this.recordVideoDemoCheckBox.addItemListener(new ItemListener()
		{
			@Override
			public void itemStateChanged(ItemEvent event)
			{
				if (drone == null) return;
				VideoRecorder videoRecorder = (VideoRecorder) drone.getImageConsumer(DroneConstants.VIDEO_RECORDER_KEY);

				if (videoRecorder == null) return;

				try
				{
					if (event.getStateChange() == ItemEvent.SELECTED)
					{
						videoRecorder.start();
						recordVideoDemoCheckBox.setForeground(Color.RED);
						aviItem.setEnabled(false);
						movItem.setEnabled(false);

					}
					else
					{
						videoRecorder.stop();
						recordVideoDemoCheckBox.setForeground(Color.BLACK);
						aviItem.setEnabled(true);
						movItem.setEnabled(true);
					}
				}
				catch (IOException e)
				{
					e.printStackTrace();
				}
			}

		});
		

		this.detectTargetCheckBox = new JCheckBox("detect Targets");
		this.detectTargetCheckBox.setForeground(JMSwingUtilities.invertColor(DEFAULT_BACKGROUND_COLOR));
		
		/*
		if (drone != null)
		{
			String detectConfig = drone.getConfiguration("DETECT:detect_type");
			System.err.println(detectConfig);
			if (detectConfig != null)
			{
				if (detectConfig.equals("2"))
						this.detectTargetCheckBox.setSelected(true);
			}

		}
		*/
		
		this.detectTargetCheckBox.addItemListener(new ItemListener()
		{
			@Override
			public void itemStateChanged(ItemEvent event)
			{
				if (drone == null) return;
				
				if (event.getStateChange() == ItemEvent.SELECTED)
				{
					AtConfigCommand c = new AtConfigCommand();
					c.param("detect:enemy_colors", "3");
					drone.sendScheduled(c);
					c.param("detect:detections_select_h", "1");
					drone.sendScheduled(c);
					c.param("detect:detect_type", "2");
					drone.sendScheduled(c);
				}
				else
				{
					AtConfigCommand c = new AtConfigCommand();
					c.param("detect:detect_type", "0");
					drone.sendScheduled(c);					
				}
			}
			
		});

		this.recordVideoPanel.add(this.detectTargetCheckBox);
		this.recordVideoPanel.add(this.recordVideoDemoCheckBox);
		this.recordVideoPanel.add(this.takePictureButton);
		
		return this.recordVideoPanel;
	}

	private void setupPageCenterPane()
	{
		this.pageCenterPane = new JPanel();
		this.pageCenterPane.setLayout(new BoxLayout(this.pageCenterPane, BoxLayout.Y_AXIS));
		
		this.pageCenterPane.add(this.setupVideoSelectionPane());
		this.pageCenterPane.add(this.setupVideoPane());
		this.pageCenterPane.add(this.setupRecordVideoPanel());

		this.getContentPane().add(this.pageCenterPane, BorderLayout.CENTER);
	}

	private void setupPageEndPane()
	{
		this.pageEndPane = new JPanel();
		this.pageEndPane.setLayout(new BoxLayout(this.pageEndPane, BoxLayout.X_AXIS));

		this.pageEndPane.add(this.setupFlightInstrumentPane());

		this.getContentPane().add(this.pageEndPane, BorderLayout.PAGE_END);
	}

	public CockpitUpdater updater()
	{
		return this.cockpitUpdater;
	}

	public final class CockpitUpdater implements OptionsVistor, ImageConsumer
	{
		private TagsDraft tagsDraft = new TagsDraft();
		
		@Override
		public void consume(BufferedImage image)
		{
			video.update(image);
		}

		@Override
		public void visitNavdataDemo(NavdataDemo navdataDemo)
		{
			vxIndicator.setValue((int) navdataDemo.vx);
			vyIndicator.setValue((int) navdataDemo.vy);
			vzIndicator.setValue((int)navdataDemo.vz);

			batteryIndicator.setValue((int) navdataDemo.vbat_flying_percentage);
			altitudeIndicator.setValue(navdataDemo.altitude / TO_M);

			compass.getModel().setValue((int) navdataDemo.psi / TO_DEG);
			horizon.getModel().setValue("roll", (int) navdataDemo.phi / TO_DEG);

			int pitch = (int) navdataDemo.theta / TO_DEG;
			if (pitch > horizon.getModel().getMaximum("pitch")) pitch = horizon.getModel().getMaximum("pitch");
			if (pitch < horizon.getModel().getMinimum("pitch")) pitch = horizon.getModel().getMinimum("pitch");
			horizon.getModel().setValue("pitch", pitch);
		}

		@Override
		public void visitVisionDetect(VisionDetect navdataVisionDetect)
		{
			if(navdataVisionDetect.nb_detected > 0)
			{
				System.out.println(navdataVisionDetect);
				this.tagsDraft.setData(navdataVisionDetect);
				video.addDraft(this.tagsDraft);
			}
		};
	}

	private void closeCockpit()
	{
		if (drone != null) drone.terminate();
	}

	public static void main(String[] args)
	{
		final Cockpit cockpit = new Cockpit(null);

		EventQueue.invokeLater(new Runnable()
		{
			public void run()
			{
				cockpit.setup();
			}
		});
	}
}
